package com.raft.core.node.store;

import java.io.File;
import java.io.IOException;

import javax.annotation.concurrent.NotThreadSafe;

import com.raft.core.node.NodeId;
import com.raft.core.schedule.Files;
import com.raft.core.support.RandomAccessFileAdapter;
import com.raft.core.support.SeekableFile;

/**基于文件的，本书使用二进制格式存放两个数据，个数：
 * 4字节：currentTerm
 * 4字节：votedFor长度
 * 变长：votedFor内容
 * @author zhangsulei
 *
 */
@NotThreadSafe
public class FileNodeStore implements NodeStore {

	// 文件名
    public static final String FILE_NAME = "node.bin";
    private static final long OFFSET_TERM = 0;
    private static final long OFFSET_VOTED_FOR = 4;
    private final SeekableFile seekableFile;
    // currentTerm和votedFor在重启后会恢复到之前的状态。不恢复currentTerm看起来也没什么问题，不管是收到Leader节点的心跳信息，还是发起选举时得到最新的选举term，系统都会获得
    // 最新的值。但是votedFor不同：
    // 假如节点B在给节点A投票后重启，收到来自候选节点D的消息后再次投票，就违反了一票制，所以节点B再次重启后恢复之前的已投票的节点(votedFor)，同时对应的currentTerm也必须恢复。
    private int term = 0;
    private NodeId votedFor = null;

    /**从文件读取
     * @param file
     */
    public FileNodeStore(File file) {
        try {
        	// 如果文件不存在，创建文件
            if (!file.exists()) {
                Files.touch(file);
            }
            seekableFile = new RandomAccessFileAdapter(file);
            initializeOrLoad();
        } catch (IOException e) {
            throw new NodeStoreException(e);
        }
    }

    /**从模拟文件读取，用于测试
     * @param seekableFile
     */
    public FileNodeStore(SeekableFile seekableFile) {
        this.seekableFile = seekableFile;
        try {
            initializeOrLoad();
        } catch (IOException e) {
            throw new NodeStoreException(e);
        }
    }

    /**初始化或者加载
     * @throws IOException
     */
    private void initializeOrLoad() throws IOException {
        if (seekableFile.size() == 0) {
            // (term, 4) + (votedFor length, 4) = 8
            seekableFile.truncate(8L);
            seekableFile.seek(0);
            seekableFile.writeInt(0); // term
            seekableFile.writeInt(0); // votedFor length
        } else {
            // 读取term
            term = seekableFile.readInt();
            // 读取votedFor
            int length = seekableFile.readInt();
            if (length > 0) {
                byte[] bytes = new byte[length];
                seekableFile.read(bytes);
                votedFor = new NodeId(new String(bytes));
            }
        }
    }

    @Override
    public int getTerm() {
        return term;
    }

    @Override
    public void setTerm(int term) {
        try {
            seekableFile.seek(OFFSET_TERM);
            seekableFile.writeInt(term);
        } catch (IOException e) {
            throw new NodeStoreException(e);
        }
        this.term = term;
    }

    @Override
    public NodeId getVotedFor() {
        return votedFor;
    }

    @Override
    public void setVotedFor(NodeId votedFor) {
        try {
            seekableFile.seek(OFFSET_VOTED_FOR);
            if (votedFor == null) {
                seekableFile.writeInt(0);
            } else {
                byte[] bytes = votedFor.getValue().getBytes();
                seekableFile.writeInt(bytes.length);
                seekableFile.write(bytes);
            }
        } catch (IOException e) {
            throw new NodeStoreException(e);
        }
        this.votedFor = votedFor;
    }

    @Override
    public void close() {
        try {
            seekableFile.close();
        } catch (IOException e) {
            throw new NodeStoreException(e);
        }
    }

}
